package switchviz.graphic.gauge.tacho;

import org.jdom.Element;

import switchviz.graphic.gauge.GaugeDataModel;
import switchviz.graphic.gauge.IGauge;
import switchviz.graphic.gauge.tacho.Tacho3dMultiDataModel.AlarmMode;
import switchviz.graphic.gauge.tacho.scale.TachoScale;
import switchviz.graphic.gauge.tacho.scale.TachoScaleDataModel;

import java.util.List;

import javax.media.opengl.GL;

import javax.swing.JPanel;


public class Tacho3dMulti extends IGauge
{

    //~ Instance fields ----------------------------------------------------------------------------

    private TachoScale _scale = null;
    private TachoScaleDataModel _scalemodel = null;
    private Tacho3dMultiDataModel _tachomodel = null;
    private Tacho3dMultiPanel _panel = null;
    private Tacho3dMultiPersistence _nodehandler = null;
    private Data[] _list = null;
    private float[][] _color;
    private boolean _enable = true;
    private int _detaillevel = 0;
    private float _fac;
    private int _buffersize = 0;
    private float _scaledepth;
    private float _depthstep;
    private float _anglerad;
    
    private boolean[] _alarmupperon;
    private boolean[] _alarmloweron;
    private boolean _freeze = false;
    private boolean _flash = false;
    private long _flashtimer = 0;

    //~ Constructors -------------------------------------------------------------------------------

    public Tacho3dMulti(String type, Element node)
    {
        super(type, node);

        init();

        if (getVisibleSignals() != null)
        {
            _list = new Data[getVisibleSignals().length];

            for (int i = 0; i < getVisibleSignals().length; ++i)
            {
                _list[i] = new Data(_buffersize, _tachomodel.getGranularity(), getVisibleSignals()[i].isLogarithmic());
            }
        }
    }

    //~ Methods ------------------------------------------------------------------------------------

    private void init()
    {
        _nodehandler = new Tacho3dMultiPersistence(_root);

        if (_tachomodel != null)
        {
            int port = _tachomodel.getPort();

            _tachomodel = _nodehandler.getDataModel();
            _tachomodel.setPort(port);
        }
        else
        {
            _tachomodel = _nodehandler.getDataModel();
        }
        
        if (_buffersize == 0)
            _buffersize = _tachomodel.getDataLength();
        
        _scale = new TachoScale(_nodehandler.getScaleElement(), _tachomodel.getScaleMode(),
                _tachomodel.getDataMin(), _tachomodel.getDataMax(), _enable);
        _scalemodel = _scale.getDataModel();
        _fac = (float) Math.PI / 8;
        _scaledepth = _scalemodel.getDepth();
        _depthstep = _scaledepth / _buffersize;
        _anglerad = (float) Math.toRadians(_scalemodel.getAngle());

        if (getVisibleSignals() != null)
        {
            _alarmupperon = new boolean[getVisibleSignals().length];
            _alarmloweron = new boolean[getVisibleSignals().length];

            for (int i = 0; i < getVisibleSignals().length; ++i)
            {
            	
                switch (getVisibleSignals()[i].getAlarmmode())
                {
                case 1:
                    _alarmupperon[i] = true;

                    break;

                case 2:
                    _alarmloweron[i] = true;

                    break;

                case 3:
                    _alarmloweron[i] = true;
                    _alarmupperon[i] = true;

                    break;
                }
            }
        }
    }

    private void updateSignal()
    {
        if (!_freeze && (getVisibleSignals() != null))
        {
            for (int i = 0; i < getVisibleSignals().length; i++)
            {
                if (_list.length == 0)
                {
                    return;
                }

                _list[i].add(getVisibleSignals()[i].getValue());
            }
        }
    }

    public void draw(GL gl)
    {
        if (isConnected() != _enable)
        {
            setEnable(isConnected());
        }
        
        gl.glPointSize(1.0f);
        _scale.draw(gl);

        if (!_enable)
            return;

        updateSignal();
        
        boolean[] max = new boolean[_list.length];
        float scalestart = _scalemodel.getScaleStart() + 90;
        
        gl.glPushMatrix();

        gl.glRotatef(-scalestart, 0.0f, 0.0f, 1.0f);
        gl.glPointSize(_tachomodel.getPointsize());

        for (int i = 0; i < _list.length; i++)
        {
            if ((getVisibleSignals()[i].getPriority() > _detaillevel) ||
                    !getVisibleSignals()[i].isEnable())
            {
                continue;
            }

            float r = _color[i][0];
            float g = _color[i][1];
            float b = _color[i][2];
            float z = -_scaledepth;
            
            int pointer = _list[i].bufferPointer;

            for (int j = 0; j < _buffersize; j++)
            {
                z += _depthstep;

                gl.glBegin(GL.GL_POINTS);
                
                gl.glColor4f(r, g, b, 0.5f * ((_scaledepth + z) / _scaledepth));

                if ((_list[i].values[pointer] > 0) &&
                        (_list[i].values[pointer] < 1))
                {
                    double s;

                   
                    s = ((_list[i].values[pointer]) * _anglerad) / 1;

                    gl.glVertex3f((float) Math.sin(s), (float) Math.cos(s), z);
                }
                else if (1 < _list[i].values[pointer])
                {
                    float s = _anglerad + 0.087f + (0.0349f * i);

                    gl.glVertex3f((float) Math.sin(s), (float) Math.cos(s), z);
                }

                pointer = ++pointer % _buffersize;
            }
            
            gl.glEnd();

            
            gl.glColor4f(r, g, b, 1.0f);
            
            
            float rot = 0.0f;

            if (_list[i].recentValue <= 0)
            {
            	rot = -6.0f - (i * 2.0f);
            }
            else if (_list[i].recentValue >= 1)
            {
                rot = _scalemodel.getAngle() + 5.0f + (i * 2.0f);
 
                max[i] = true;
            }
            else
            {
                rot = (_list[i].recentValue * _scalemodel.getAngle()) / 1;
            }

            gl.glRotatef(-rot, 0.0f, 0.0f, 1.0f);


            
           	if (_tachomodel.getAlarmMode() == AlarmMode.FLASHING && _flash && 
           			((_alarmupperon[i] && (getVisibleSignals()[i].getMax() < _list[i].recentValue)) || 
           					(_alarmloweron[i] && (getVisibleSignals()[i].getMin() > _list[i].recentValue))))
           	{
	           	gl.glBegin(GL.GL_QUADS);
	           	
	           	for (int j = 0; j < (16 / 2); j++)
	            {
	           		gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	           		gl.glVertex3d(_tachomodel.getNeedleWidth() * Math.cos(_fac * j),
	                   		-_tachomodel.getNeedleWidth() * Math.sin(_fac * j), 0.0f);
	                gl.glVertex3d(_tachomodel.getNeedleWidth() * Math.cos(_fac * (j + 1)),
	                   		-_tachomodel.getNeedleWidth() * Math.sin(_fac * (j + 1)), 0.0f);
	           		
	                gl.glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
	                gl.glVertex3d(1.5f*_tachomodel.getNeedleWidth() * Math.cos(_fac * (j + 1)),
	                   		1.5f*-_tachomodel.getNeedleWidth() * Math.sin(_fac * (j + 1)), 0.0f);
	           		gl.glVertex3d(1.5f*_tachomodel.getNeedleWidth() * Math.cos(_fac * j),
	                   		1.5f*-_tachomodel.getNeedleWidth() * Math.sin(_fac * j), 0.0f);
	            }
	           	
	           	gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	           	gl.glVertex3d(-_tachomodel.getNeedleWidth(), 0.0, 0.0f);
	           	gl.glVertex3d(0.0, 1.0, 0.0f);
	           	gl.glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
	           	gl.glVertex3d(0.0, 1.05, 0.0f);
	           	gl.glVertex3d(1.5f*-_tachomodel.getNeedleWidth(), 0.0, 0.0f);
	           	
	           	gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	           	gl.glVertex3d(_tachomodel.getNeedleWidth(), 0.0, 0.0f);
	           	gl.glVertex3d(0.0, 1.0, 0.0f);
	            	gl.glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
	           	gl.glVertex3d(0.0, 1.05, 0.0f);
	           	gl.glVertex3d(1.5f*_tachomodel.getNeedleWidth(), 0.0, 0.0f);
	           	
	           	gl.glEnd();
	           	
	           	gl.glColor4f(0.3f+r, 0.3f+g, 0.3f+b, 1.0f);
           	}
            
            gl.glBegin(GL.GL_TRIANGLES);
            
            for (int j = 0; j < (16 / 2); j++)
            {
                gl.glVertex3d(_tachomodel.getNeedleWidth() * Math.cos(_fac * j),
                    -_tachomodel.getNeedleWidth() * Math.sin(_fac * j), 0.0f);
                gl.glVertex3d(_tachomodel.getNeedleWidth() * Math.cos(_fac * (j + 1)),
                    -_tachomodel.getNeedleWidth() * Math.sin(_fac * (j + 1)), 0.0f);
                gl.glVertex3d(0.0, 0.0, 0.0f);
            }

            gl.glVertex3d(-_tachomodel.getNeedleWidth(), 0.0, 0.0f);
            gl.glVertex3d(_tachomodel.getNeedleWidth(), 0.0, 0.0f);
            gl.glVertex3d(0.0, 1.0, 0.0f);

            gl.glEnd();
            
            
            gl.glRotatef(rot, 0.0f, 0.0f, 1.0f);
            gl.glTranslatef(0.0f, 0.0f, 0.01f);
        }
        
        for (int i = 0; i < _list.length; i++)
        {
        	float s = _anglerad + 0.087f + (0.0349f * i);
        	
        	if (max[i])
           	{
        		gl.glTranslatef(0.0f, 0.0f, 0.01f);
        		
        		gl.glBegin(GL.GL_TRIANGLES);
        		
        		if (_flash)
        		{
        			gl.glColor4f(_color[i][0], _color[i][1], _color[i][2], 1.0f);
        		}
        		else
        		{
        			gl.glColor4f(_color[i][0], _color[i][1], _color[i][2], 0.8f);
        		}
        		
        		for (int k = 0; k < 16; k++)
        		{
        			gl.glVertex3d((0.04f * Math.cos(_fac * k)) + 1.1*Math.sin(s),
                          (0.04f * Math.sin(_fac * k)) + 1.1*Math.cos(s), 
                          0.0f);
        			gl.glVertex3d((0.04f * Math.cos(_fac * (k + 1))) + 1.1*Math.sin(s),
                          (0.04f * Math.sin(_fac * (k + 1))) + 1.1*Math.cos(s), 
                          0.0f);
        			gl.glVertex3d(1.1*Math.sin(s), 
        					1.1*Math.cos(s), 
        					0.0f);
        		}
        		
        		gl.glEnd();
        		
        		gl.glBegin(GL.GL_QUADS);
        		
        		if (_flash)
        		{
        			for (int k = 0; k < 16; k++)
                    {
        				gl.glColor4f(_color[i][0], _color[i][1], _color[i][2], 1.0f);
        				
        				gl.glVertex3d((0.04f * Math.cos(_fac * k)) + 1.1*Math.sin(s),
                                (0.04f * Math.sin(_fac * k)) + 1.1*Math.cos(s), 
                                0.0f);
              			gl.glVertex3d((0.04f * Math.cos(_fac * (k + 1))) + 1.1*Math.sin(s),
                                (0.04f * Math.sin(_fac * (k + 1))) + 1.1*Math.cos(s), 
                                0.0f);
                        
        				
                        gl.glColor4f(_color[i][0], _color[i][1], _color[i][2], 0.0f);
        				
                        gl.glVertex3d((0.05f * Math.cos(_fac * (k + 1))) + 1.1*Math.sin(s),
                                (0.05f * Math.sin(_fac * (k + 1))) + 1.1*Math.cos(s), 
                                0.0f);
                        gl.glVertex3d((0.05f * Math.cos(_fac * k)) + 1.1*Math.sin(s),
                                (0.05f * Math.sin(_fac * k)) + 1.1*Math.cos(s), 
                                0.0f);
                    }
        		}
        		
        		gl.glEnd();
        		
        		gl.glTranslatef(0.0f, 0.0f, -0.01f);
           	}
        	else
        	{
        		gl.glBegin(GL.GL_TRIANGLES);
        		gl.glColor4f(_color[i][0], _color[i][1], _color[i][2], 0.1f);
        		
        		for (int k = 0; k < 16; k++)
        		{
        			gl.glVertex3d((0.04f * Math.cos(_fac * k)) + 1.1*Math.sin(s),
                          (0.04f * Math.sin(_fac * k)) + 1.1*Math.cos(s), 
                          0.0f);
        			gl.glVertex3d((0.04f * Math.cos(_fac * (k + 1))) + 1.1*Math.sin(s),
                          (0.04f * Math.sin(_fac * (k + 1))) + 1.1*Math.cos(s), 
                          0.0f);
        			gl.glVertex3d(1.1*Math.sin(s), 
        					1.1*Math.cos(s), 
        					0.0f);
        		}
        		
        		gl.glEnd();
        	}
        }
        
        gl.glRotatef(scalestart, 0.0f, 0.0f, 1.0f);

        gl.glTranslatef(0.0f, 0.5f, 0.0f);
        
        
        if (_flashtimer < System.currentTimeMillis())
        {
        	_flash = !_flash;
        	_flashtimer = System.currentTimeMillis()+300;
        }

        gl.glPopMatrix();
    }

    public void initialize(GL gl)
    {
        _scale.initialize(gl);

        if ((getVisibleSignals() != null) && (getVisibleSignals().length > 0))
        {
            _color = new float[getVisibleSignals().length][3];

            for (int i = 0; i < getVisibleSignals().length; ++i)
            {
                _color[i] = getVisibleSignals()[i].getColor();
            }
        }
    }

    public void addSetupPanels(List<JPanel> panellist)
    {
        panellist.add(getTachoPanel());
        panellist.add(_scale.getSetupPanel());
    }

    public void setElement(Element node)
    {
        _root = node;

        init();
    }

    public void saveSetupPanels()
    {
        _nodehandler.setElement(getTachoPanel().getElement());

        Element _scalenode = _scale.getElement();

        _nodehandler.getElement()
                    .removeContent(_nodehandler.getElement().getChild(Tacho3dMultiPersistence.SCALE));
        _nodehandler.getElement().addContent(_scalenode);

        _root = _nodehandler.getElement();

        init();
    }

    private Tacho3dMultiPanel getTachoPanel()
    {
        if (_panel == null)
        {
            _panel = new Tacho3dMultiPanel(_nodehandler.getElement());
        }

        return _panel;
    }

    public GaugeDataModel getGaugeDataModel()
    {
        return _tachomodel;
    }

    @Override
    public void setEnable(boolean enable)
    {
        _enable = enable;

        init();
    }

    @Override
    public void setDetailLevel(int level)
    {
        _detaillevel = level;
    }

    @Override
    public void resetData()
    {
        _buffersize = _tachomodel.getDataLength();

        if (getVisibleSignals() != null)
        {
            _list = new Data[getVisibleSignals().length];

            for (int i = 0; i < getVisibleSignals().length; ++i)
            {
                _list[i] = new Data(_buffersize, _tachomodel.getGranularity(), getVisibleSignals()[i].isLogarithmic());
            }
        }
    }

    @Override
    public void setFreeze(boolean b)
    {
        _freeze = b;
    }

    //~ Inner Classes ------------------------------------------------------------------------------

    private class Data
    {
        private int bufferSize;
        private int granularity;
        private int granularityCounter = 0;
        public int bufferPointer = 0;
        public float recentValue = 0.0f;
        public float[] values;
        
        private float needleSpeed;
        private float destinationValue;
        private boolean log;
        
        public Data(int size, int granularity, boolean log)
        {
            this.values = new float[size];
            this.bufferSize = size;
            this.granularity = granularity;
            this.log = log;
        }

        public void add(float degree)
        {
        	if (log)
        		destinationValue = (float)((Math.log10(degree) - Math.log10(_tachomodel.getDataMin()))/(Math.log10(_tachomodel.getDataMax())-Math.log10(_tachomodel.getDataMin())));
        	else
        		destinationValue = (degree - _tachomodel.getDataMin())/(_tachomodel.getDataMax()-_tachomodel.getDataMin());
        	
        	
            if (granularityCounter == 0)
            {
                values[bufferPointer] = recentValue;

                bufferPointer = ++bufferPointer % bufferSize;
            }

            granularityCounter = ++granularityCounter % granularity;


            if (recentValue + needleSpeed < destinationValue)
	           	needleSpeed += 0.001;
            else
	            needleSpeed -= 0.001;
            
            
            needleSpeed *= 0.9;
            
            if (recentValue < _tachomodel.getDataMin())
            {
            	recentValue = _tachomodel.getDataMin();
            	needleSpeed = 0;
            }
            
            recentValue += (Math.abs(needleSpeed)>0.001)?needleSpeed:0;
        }
        
        
    }
}
